package com.creditcode.xxj.util;

import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;

import java.util.Map;
import java.util.regex.Pattern;

/**
 * 统一社会信用代码（GB32100-2015）工具类<br>
 * 标准见：https://www.cods.org.cn/c/2020-10-29/12575.html
 *
 * <pre>
 * 第一部分：登记管理部门代码1位 (数字或大写英文字母)
 * 第二部分：机构类别代码1位 (数字或大写英文字母)
 * 第三部分：登记管理机关行政区划码6位 (数字)
 * 第四部分：主体标识码（组织机构代码）9位 (数字或大写英文字母)
 * 第五部分：校验码1位 (数字或大写英文字母)
 * </pre>
 *
 * @author looly
 * @since 5.2.4
 */
public class CustomCreditCodeUtils {
    /**
     *登记管理部门代码 和其下 登记管理部门的机构类别代码
     */
    private static final Map<Character,String> DEPT_WITH_CODE;

    public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE;

    /**
     * 统一社会信用代码 加权因子
     */
    private static final int[] CREDIT_CODE_WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28};
    /**
     * 主体标识码（组织机构代码）加权因子
     */
    private static final int[] ORGAN_CODE_WEIGHT = {3, 7, 9, 10, 5, 8, 4, 2};
    /**
     * GB 32100-2015 法人和其他组织统一社会信用代码编码规则 代码字符集（30个）
     *
     * @link https://www.cods.org.cn/c/2020-10-29/12575.html
     */
    private static final char[] CREDIT_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray();
    /**
     * GB11714 全国组织机构代码编码字符集 （35个）
     *
     * @link https://www.cods.org.cn/c/2020-10-29/12582.html
     */
    private static final char[] ORGAN_CODE_ARRAY = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private static final Map<Character, Integer> CREDIT_CODE_INDEX_MAP;
    private static final Map<Character, Integer> ORGAN_CODE_INDEX_MAP;

    static {
        CREDIT_CODE_INDEX_MAP = new SafeConcurrentHashMap<>(CREDIT_CODE_ARRAY.length);
        for (int i = 0; i < CREDIT_CODE_ARRAY.length; i++) {
            CREDIT_CODE_INDEX_MAP.put(CREDIT_CODE_ARRAY[i], i);
        }
        ORGAN_CODE_INDEX_MAP = new SafeConcurrentHashMap<>(ORGAN_CODE_ARRAY.length);
        for (int i = 0; i < ORGAN_CODE_ARRAY.length; i++) {
            ORGAN_CODE_INDEX_MAP.put(ORGAN_CODE_ARRAY[i], i);
        }
        //TODO 此处将组织机构代码字符集中删除掉了几个社会信用代码中不存在的字符集，保证社会信用代码校验能不异常
        ORGAN_CODE_INDEX_MAP.remove("I");
        ORGAN_CODE_INDEX_MAP.remove("S");
        ORGAN_CODE_INDEX_MAP.remove("O");
        ORGAN_CODE_INDEX_MAP.remove("V");
        ORGAN_CODE_INDEX_MAP.remove("Z");
        DEPT_WITH_CODE = new SafeConcurrentHashMap<>();
        /**
         * key-机构编制->1
         * value
         * 机关--1
         * 事业单位--2
         * 编办直接管理机构编制的群众团体--3
         * 其他--9
         */
        DEPT_WITH_CODE.put('1', "1239");
        /**
         * key-外交->2
         * value
         * 外国常驻新闻机构--1
         * 其他--9
         */
        DEPT_WITH_CODE.put('2', "19");
        /**
         * key-司法行政 3
         * value
         * 律师执业机构 1
         * 公证处 2
         * 基层法律服务所 3
         * 司法鉴定机构 4
         * 仲裁委员会 5
         * 其他 9
         */
        DEPT_WITH_CODE.put('3', "123459");
        /**
         * key-文化 4
         * value
         * 外国在华文化中心 1
         * 其他 9
         */
        DEPT_WITH_CODE.put('4', "19");
        /**
         * key-民政 5
         * value
         * 社会团体 1
         * 民办非企业单位 2
         * 基金会 3
         * 其他 9
         */
        DEPT_WITH_CODE.put('5', "1239");
        /**
         * key-旅游 6
         * value
         * 外国旅游部门常驻代表机构 1
         * 港澳台地区旅游部门常驻内地（大陆）代表机构 2
         * 其他 9
         */
        DEPT_WITH_CODE.put('6', "129");
        /**
         * key-宗教 7
         * value
         * 宗教活动场所 1
         * 宗教院校 2
         * 其他 9
         */
        DEPT_WITH_CODE.put('7', "129");
        /**
         * key-工会 8
         * value
         * 基层工会 1
         * 其他 9
         */
        DEPT_WITH_CODE.put('8', "19");
        /**
         * key-工商 9
         * value
         * 企业 1
         * 个体工商户 2
         * 农民专业合作社 3
         */
        DEPT_WITH_CODE.put('9', "123");
        /**
         * key-中央军委改革和编制办公室 A
         * value
         * 军队事业单位 1
         * 其他 9
         */
        DEPT_WITH_CODE.put('A', "19");
        /**
         * key-农业 N
         * value
         * 组级集体经济组织 1
         * 村级集体经济组织 2
         * 乡镇级集体经济组织 3
         * 其他 9
         */
        DEPT_WITH_CODE.put('N', "1239");
        /**
         * key-其他 Y
         * value
         * 其他 1
         */
        DEPT_WITH_CODE.put('Y', "1");
    }

    /**
     * 正则校验统一社会信用代码（18位）
     *
     * <pre>
     * 第一部分：登记管理部门代码1位 (数字或大写英文字母)
     * 第二部分：机构类别代码1位 (数字或大写英文字母)
     * 第三部分：登记管理机关行政区划码6位 (数字)
     * 第四部分：主体标识码（组织机构代码）9位 (数字或大写英文字母)
     * 第五部分：校验码1位 (数字或大写英文字母)
     * </pre>
     *
     * @param creditCode 统一社会信用代码
     * @return 校验结果
     */
    public static boolean isCreditCodeSimple (CharSequence creditCode) {
        if (StrUtil.isBlank(creditCode)) {
            return false;
        }
        return ReUtil.isMatch(CREDIT_CODE_PATTERN, creditCode);
    }

    /**
     * 是否是有效的统一社会信用代码
     * <pre>
     * 第一部分：登记管理部门代码1位 (数字或大写英文字母)
     * 第二部分：机构类别代码1位 (数字或大写英文字母)
     * 第三部分：登记管理机关行政区划码6位 (数字)
     * 第四部分：主体标识码（组织机构代码）9位 (数字或大写英文字母)
     * 第五部分：校验码1位 (数字或大写英文字母)
     * </pre>
     *
     * @param creditCode 统一社会信用代码
     * @return 校验结果
     */
    public static String isCreditCode (CharSequence creditCode) {
        if (!isCreditCodeSimple(creditCode)) {
            return "社会统一信用代码初步正则匹配格式校验未通过";
        }
        if (!validateDeptCode(creditCode.subSequence(0, 2))){
            return "第一位登记部门代码校验和第二位机构类别代码校验未通过";
        }
        final int creditCodeParityBit = getCreditCodeParityBit(creditCode);
        if (creditCodeParityBit < 0) {
            return "组织机构代码的校验码验证未通过";
        }

        final int organCodeParityBit = getOrganCodeParityBit(creditCode.subSequence(8, 16));
        if (organCodeParityBit < 0) {
            return "社会信用代码校验码验证未通过";
        }
        final boolean parityBitValidate = creditCode.charAt(17) == CREDIT_CODE_ARRAY[creditCodeParityBit]
                && creditCode.charAt(16) == ORGAN_CODE_ARRAY[organCodeParityBit];
        if (parityBitValidate){
            return "合格的统一社会信用代码";
        }
        return "不合格的统一社会信用代码";
    }

    /**
     * 功能描述:
     * 校验 第一位登记管理部门代码和第二位机构类别代码
     * @param creditCode 统一社会信用代码截取的前两位
     * @return boolean 验证结果是否正确
     * @date 2023-04-04 19:37
     * @author xxj
     */
    private static boolean validateDeptCode (CharSequence creditCode) {
        final boolean deptCodeAnyMatch =
                DEPT_WITH_CODE.keySet().stream().anyMatch(character -> StrUtil.equals(character.toString(),
                        creditCode.subSequence(0,1)));
        if (deptCodeAnyMatch){
            final Character key =
                    DEPT_WITH_CODE.keySet().stream().filter(character -> character == creditCode.charAt(0)).findFirst().get();
            final String deptCodes = DEPT_WITH_CODE.get(key);
            return deptCodes.contains(creditCode.subSequence(1, 2).toString());
        }
        return false;
    }

    /**
     * 获取一个随机的统一社会信用代码
     *
     * @return 统一社会信用代码
     */
    public static String randomCreditCode (String firtstChar,String secendChar) {
        final StringBuilder buf = new StringBuilder(18);
        // 第一位是部门代码，第二位是部门代码对应的代码类别
//        final Character firtstChar =
//                new ArrayList<>(DEPT_WITH_CODE.keySet()).get(RandomUtil.randomInt(DEPT_WITH_CODE.size()));
        buf.append(firtstChar);
//        final Character secendChar = DEPT_WITH_CODE.get(firtstChar)
//                .chars()
//                .mapToObj(c -> (char) c)
//                .collect(Collectors.toList())
//                .get(RandomUtil.randomInt(DEPT_WITH_CODE.get(firtstChar).length()));
        buf.append(secendChar);
        for (int i = 2; i < 8; i++) {
            int num = RandomUtil.randomInt(10);
            buf.append(CREDIT_CODE_ARRAY[num]);
        }
        for (int i = 8; i < 16; i++) {
            int num = RandomUtil.randomInt(CREDIT_CODE_ARRAY.length - 1);
            buf.append(CREDIT_CODE_ARRAY[num]);
        }
        buf.append(ORGAN_CODE_ARRAY[getOrganCodeParityBit(buf.subSequence(8, 16))]);

        String code = buf.toString();
        return code + CREDIT_CODE_ARRAY[getCreditCodeParityBit(code)];
    }

    /**
     * <p>功能描述:</br>
     * 获取组织机构代码 校验位的值</br>
     *
     * @param creditCode 统一社会信息代码
     * @return 获取校验位的值，-1表示获取错误
     * @description 此处使用 的是去除了社会信用代码不允许存在的代码之后的全国组织机构代码字符集，</br>
     * 因为统一社会信用代码中不允许有 I S O V 英文字母</br>
     * 但是 全国组织机构中除了 ISOV还有Z ，这样会导致合规的组织机构代码在统一社会信用代码中计算校验码异常</br>
     * </p>
     */
    private static int getOrganCodeParityBit (CharSequence creditCode) {
        int sum = 0;
        Integer codeIndex;
        for (int i = 0; i < 8; i++) {
            codeIndex = ORGAN_CODE_INDEX_MAP.get(creditCode.charAt(i));
            if (null == codeIndex) {
                return -1;
            }
            sum += codeIndex * ORGAN_CODE_WEIGHT[i];
        }
        final int result = 11 - sum % 11;
        // 余数为10 返回组织机构代码字符集字符串中 “X” 的下标，余数为11 返回 “0” 的下标
        return result == 10 ? 33 : (result == 11 ? 0 : result);
    }

    /**
     * 获取社会统一信用代码 校验位的值
     *
     * @param creditCode 统一社会信息代码
     * @return 获取校验位的值，-1表示获取错误
     */
    private static int getCreditCodeParityBit (CharSequence creditCode) {
        int sum = 0;
        Integer codeIndex;
        for (int i = 0; i < 17; i++) {
            codeIndex = CREDIT_CODE_INDEX_MAP.get(creditCode.charAt(i));
            if (null == codeIndex) {
                return -1;
            }
            sum += codeIndex * CREDIT_CODE_WEIGHT[i];
        }
        final int result = 31 - sum % 31;
        return result == 31 ? 0 : result;
    }

}
